package system.task.quartz;

import framework.runtime.SystemContext;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.InterruptableJob;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.UnableToInterruptJobException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StringUtils;
import system.entity.SysTaskJob;
import system.task.ExecutorContext;
import system.task.Task;
import system.task.TaskExecutor;
import system.task.TaskLogger;
import system.task.config.TaskConfig;
import system.task.defined.TaskStatus;
import system.task.errors.TaskException;
import system.task.logger.TaskLoggerBufferWriter;
import system.task.logger.TaskLoggerContext;
import system.task.logger.TaskLoggerFactory;
import system.task.logger.TaskLoggerSlf4jWriter;
import system.task.logger.TaskLoggerStdWriter;
import system.task.utils.TaskUtil;

import java.util.HashMap;
import java.util.Map;

/**
 * 任务执行器
 */
@DisallowConcurrentExecution
public class JobProcessor implements Job, InterruptableJob {

    private Thread executeThread;

    public JobProcessor() {
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        TaskConfig taskConfig = SystemContext.getBean(TaskConfig.class);

        //
        this.executeThread = Thread.currentThread();

        //
//        SysTaskJobService jobService = SystemContext.getBean(SysTaskJobService.class);
//        SysTaskJob query = new SysTaskJob();
//        query.setName(jobKey.getName());
//        SysTaskJob job = jobService.first(query);
//        if (job == null) {
//            throw new JobExecutionException("execute job but job deleted " + jobKey.getName() + " in database");
//        }
        SysTaskJob job = (SysTaskJob) jobExecutionContext.get(TaskUtil.TASK_TASK_JOB_NAME);
        if (job == null) {
            throw new JobExecutionException("execute job but job not run trigListener, skip once.");
        }
        Task task = new Task();
        BeanUtils.copyProperties(job, task);

        //
        Map<String, Object> param = new HashMap<>();
        jobExecutionContext.getJobDetail().getJobDataMap().forEach((k, v) -> {
            if (k != null) {
                String n = String.valueOf(k);
                if (!n.startsWith("__")) {
                    param.put(n, v);
                }
            }
        });

        //
        jobExecutionContext.getTrigger().getJobDataMap().forEach((k, v) -> {
            if (k != null) {
                String n = String.valueOf(k);
                if (!n.startsWith("__")) {
                    param.put(n, v);
                }
            }
        });

        //
        //jobExecutionContext.put(TaskUtil.TASK_TASK_JOB_NAME, job);
        jobExecutionContext.put(TaskUtil.TASK_PARAM_NAME, param);
        jobExecutionContext.put(TaskUtil.TASK_RUN_RESULT, 0);

        //
        String runId = (String) jobExecutionContext.get(TaskUtil.TASK_RUN_ID_NAME);
        if (runId == null) runId = "unknown";

        //
        String actuator = task.getActuator();
        if (!StringUtils.hasText(actuator)) {
            throw new JobExecutionException("error actuator " + actuator + " by " + task.getName());
        }

        //
        Logger logger = LoggerFactory.getLogger(task.getName());
        TaskLoggerContext taskLoggerContext = new TaskLoggerContext(taskConfig, task, runId);
        taskLoggerContext.getLogWriters().add(new TaskLoggerStdWriter(taskConfig, runId));
        taskLoggerContext.getLogWriters().add(new TaskLoggerSlf4jWriter(logger));
        if (taskConfig.getLogBufferMaxLine() != null && taskConfig.getLogBufferMaxLine() > 0) {
            taskLoggerContext.getLogWriters().add(new TaskLoggerBufferWriter(task, runId, taskConfig.getLogBufferMaxLine()));
        }
        try (TaskLogger log = TaskLoggerFactory.newLogger(taskLoggerContext)) {

            //
            jobExecutionContext.put(TaskUtil.TASK_RUN_LOG_NAME, log);

            //
            if (TaskStatus.DISABLED.equals(task.getStatus())) {
                if (logger.isInfoEnabled()) {
                    logger.info("Task is DISABLED exit this one");
                }
                return;
            }

            //
            ExecutorContext executorContext = new ExecutorContext(task, log, param, runId);

            //
            TaskExecutor executor = this.loadExecutor(task);

            //
            executor.run(executorContext);

            //
            int resultCode = executorContext.isAbnormal() ? 1 : 0;
            jobExecutionContext.put(TaskUtil.TASK_RUN_RESULT, resultCode);

        } catch (InterruptedException e) {
            throw new JobExecutionException("Task stop", e);
        } catch (Throwable e) {
            String msg = e.getMessage();
            if (!StringUtils.hasText(msg)) {
                msg = e.getClass().getCanonicalName();
            }
            throw new JobExecutionException("Run task " + task.getName() + " failed, throw error " + msg, e);
        }
    }

    private TaskExecutor loadExecutor(Task task) throws TaskException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        String actuator = task.getActuator();
        TaskExecutor executor;
        if (actuator.startsWith("CLASS:")) {
            executor = this.loadClass(actuator);
        } else if (actuator.startsWith("EXEC:")) {
            executor = this.loadExec(actuator);
        } else if (actuator.startsWith("BEAN:")) {
            executor = this.loadBean(actuator);
        } else if (actuator.startsWith("GET:")) {
            executor = this.loadGet(actuator);
        } else if (actuator.startsWith("POST:")) {
            executor = this.loadPost(actuator);
        } else {
            throw new TaskException("Not support actuator " + actuator + " by " + task.getName());
        }

        if (executor == null) {
            throw new TaskException("Not load actuator " + actuator + " by " + task.getName());
        }

        return executor;
    }

    private TaskExecutor loadPost(String actuator) {
        String url = actuator.substring("POST:".length());
        return new HttpPostJob(url);
    }

    private TaskExecutor loadGet(String actuator) {
        String url = actuator.substring("GET:".length());
        return new HttpGetJob(url);
    }

    private TaskExecutor loadExec(String actuator) {
        String exec = actuator.substring("EXEC:".length());
        return new ExecJob(exec);
    }

    private TaskExecutor loadClass(String actuator) throws ClassNotFoundException, TaskException, InstantiationException, IllegalAccessException {
        String cls = actuator.substring("CLASS:".length());
        Class<?> aClass = Class.forName(cls);
        if (aClass == null) {
            throw new TaskException("Not load class: " + cls);
        }
        Object obj = aClass.newInstance();
        if ((obj instanceof TaskExecutor) == false) {
            throw new TaskException("Class " + cls + " not implements TaskExecutor");
        }
        return (TaskExecutor) obj;
    }

    private TaskExecutor loadBean(String actuator) throws TaskException {
        String bean = actuator.substring("BEAN:".length());
        String camel = StringUtils.uncapitalize(bean);
        Object obj = SystemContext.getBean(camel);
        if (obj == null) {
            throw new TaskException("Not find bean: " + bean);
        }
        if ((obj instanceof TaskExecutor) == false) {
            throw new TaskException("Bean not implements TaskExecutor: " + obj);
        }
        return (TaskExecutor) obj;
    }

    @Override
    public void interrupt() throws UnableToInterruptJobException {
        Thread thread = this.executeThread;
        if (thread != null) {
            this.executeThread = null;
            thread.interrupt();
        }
    }
}
